# (C) British Crown Copyright 2010 - 2015, Met Office
#
# This file is part of Iris.
#
# Iris is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Iris is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Iris.  If not, see <http://www.gnu.org/licenses/>.
"""
Contains symbol definitions for use with :func:`iris.plot.symbols`.

"""

from __future__ import (absolute_import, division, print_function)
from six.moves import (filter, input, map, range, zip)  # noqa
import six

import itertools
import math

from matplotlib.patches import PathPatch
from matplotlib.path import Path

import numpy as np


__all__ = ('CLOUD_COVER',)


# The thickness to use for lines, circles, etc.
_THICKNESS = 0.1


def _make_merged_patch(paths):
    # Convert a list of Path instances into a single, black PathPatch.

    # Prepare empty vertex/code arrays for the merged path.
    # The vertex array is initially flat for convenient initialisation,
    # but is then reshaped to (N, 2).
    total_len = sum(len(path) for path in paths)
    all_vertices = np.empty(total_len * 2)
    all_codes = np.empty(total_len, dtype=Path.code_type)

    # Copy vertex/code details from the source paths
    all_segments = itertools.chain(*(path.iter_segments() for path in paths))
    i_vertices = 0
    i_codes = 0
    for vertices, code in all_segments:
        n_vertices = len(vertices)
        all_vertices[i_vertices:i_vertices + n_vertices] = vertices
        i_vertices += n_vertices

        n_codes = n_vertices // 2
        if code == Path.STOP:
            code = Path.MOVETO
        all_codes[i_codes:i_codes + n_codes] = code
        i_codes += n_codes

    all_vertices.shape = (total_len, 2)

    return PathPatch(Path(all_vertices, all_codes), facecolor='black',
                     edgecolor='none')


def _ring_path():
    # Returns a Path for a hollow ring.
    # The outer radius is 1, the inner radius is 1 - _THICKNESS.
    circle = Path.unit_circle()
    inner_radius = 1.0 - _THICKNESS
    vertices = np.concatenate([circle.vertices[:-1],
                               circle.vertices[-2::-1] * inner_radius])
    codes = np.concatenate([circle.codes[:-1], circle.codes[:-1]])
    return Path(vertices, codes)


def _vertical_bar_path():
    # Returns a Path for a vertical rectangle, with width _THICKNESS, that will
    # nicely overlap the result of _ring_path().
    width = _THICKNESS / 2.0
    inner_radius = 1.0 - _THICKNESS
    vertices = np.array([
        [-width, -inner_radius],
        [width, -inner_radius],
        [width, inner_radius],
        [-width, inner_radius],
        [-width, inner_radius]
    ])
    codes = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO,
                      Path.CLOSEPOLY])
    return Path(vertices, codes)


def _slot_path():
    # Returns a Path for a filled unit circle with a vertical rectangle
    # removed.
    circle = Path.unit_circle()
    vertical_bar = _vertical_bar_path()
    vertices = np.concatenate([circle.vertices[:-1],
                               vertical_bar.vertices[-2::-1]])
    codes = np.concatenate([circle.codes[:-1], vertical_bar.codes[:-1]])
    return Path(vertices, codes)


def _left_bar_path():
    # Returns a Path for the left-hand side of a horizontal rectangle, with
    # height _THICKNESS, that will nicely overlap the result of _ring_path().
    inner_radius = 1.0 - _THICKNESS
    height = _THICKNESS / 2.0
    vertices = np.array([
        [-inner_radius, -height],
        [0, -height],
        [0, height],
        [-inner_radius, height],
        [-inner_radius, height]
    ])
    codes = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO,
                      Path.CLOSEPOLY])
    return Path(vertices, codes)


def _slash_path():
    # Returns a Path for diagonal, bottom-left to top-right rectangle, with
    # width _THICKNESS, that will nicely overlap the result of _ring_path().
    half_width = _THICKNESS / 2.0
    central_radius = 1.0 - half_width

    cos45 = math.cos(math.radians(45))

    end_point_offset = cos45 * central_radius
    half_width_offset = cos45 * half_width

    vertices = np.array([
        [-end_point_offset - half_width_offset,
         -end_point_offset + half_width_offset],
        [-end_point_offset + half_width_offset,
         -end_point_offset - half_width_offset],
        [end_point_offset + half_width_offset,
         end_point_offset - half_width_offset],
        [end_point_offset - half_width_offset,
         end_point_offset + half_width_offset],
        [-end_point_offset - half_width_offset,
         -end_point_offset + half_width_offset]
    ])
    codes = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO,
                      Path.CLOSEPOLY])
    return Path(vertices, codes)


def _backslash_path():
    # Returns a Path for diagonal, top-left to bottom-right rectangle, with
    # width _THICKNESS, that will nicely overlap the result of _ring_path().
    half_width = _THICKNESS / 2.0
    central_radius = 1.0 - half_width

    cos45 = math.cos(math.radians(45))

    end_point_offset = cos45 * central_radius
    half_width_offset = cos45 * half_width

    vertices = np.array([
        [-end_point_offset - half_width_offset,
         end_point_offset - half_width_offset],
        [end_point_offset - half_width_offset,
         -end_point_offset - half_width_offset],
        [end_point_offset + half_width_offset,
         -end_point_offset + half_width_offset],
        [-end_point_offset + half_width_offset,
         end_point_offset + half_width_offset],
        [-end_point_offset - half_width_offset,
         end_point_offset - half_width_offset]
    ])
    codes = np.array([Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO,
                      Path.CLOSEPOLY])
    return Path(vertices, codes)


def _wedge_fix(wedge_path):
    '''
    Fixes the problem with Path.wedge where it doesn't initialise the first,
    and last two vertices.
    This fix should not have any side-effects once Path.wedge has been fixed,
    but will then be redundant and should be removed.

    This is fixed in MPL v1.3, raising a RuntimeError. A check is performed to
    allow for backward compatibility with MPL v1.2.x.

    '''
    if wedge_path.vertices.flags.writeable:
        wedge_path.vertices[0] = 0
        wedge_path.vertices[-2:] = 0
    return wedge_path


CLOUD_COVER = {
    0: [_ring_path()],
    1: [_ring_path(), _vertical_bar_path()],
    2: [_ring_path(), _wedge_fix(Path.wedge(0, 90))],
    3: [_ring_path(), _wedge_fix(Path.wedge(0, 90)), _vertical_bar_path()],
    4: [_ring_path(), Path.unit_circle_righthalf()],
    5: [_ring_path(), Path.unit_circle_righthalf(), _left_bar_path()],
    6: [_ring_path(), _wedge_fix(Path.wedge(-180, 90))],
    7: [_slot_path()],
    8: [Path.unit_circle()],
    9: [_ring_path(), _slash_path(), _backslash_path()],
}
"""
A dictionary mapping WMO cloud cover codes to their corresponding symbol.

See http://www.wmo.int/pages/prog/www/DPFS/documents/485_Vol_I_en_colour.pdf
    Part II, Appendix II.4, Graphical Representation of Data, Analyses
    and Forecasts

"""


def _convert_paths_to_patches():
    # Convert the symbols defined as lists-of-paths into patches.
    for code, symbol in six.iteritems(CLOUD_COVER):
        CLOUD_COVER[code] = _make_merged_patch(symbol)


_convert_paths_to_patches()
